/*
 * @Author: soso
 * @Date: 2022-01-21 16:40:00
 * @LastEditTime: 2022-03-11 10:47:41
 * @LastEditors: Please set LastEditors
 * @Description: 操作日志
 * @FilePath: /go-mesh-sync/core/cfiles/cmds.go
 */

package cfiles

import (
	"context"
	"errors"
	"gomeshsync/cfile"
	"gomeshsync/config"
	"gomeshsync/consts"
	"io/fs"
	"io/ioutil"
	"log"
	"os"
	"sync"
	"time"

	"gitee.com/kimsoso/go-utils/utils"
	"github.com/vmihailenco/msgpack"
)

func createTemporaryCmds(newCmds []*cfile.MCommand) (err error) {
	if TempCmds.Len() > 0 {
		return errors.New("already have temp commands")
	} else {
		TempCmds.Adds(newCmds)
		return nil
	}
}

/**
 * @description: 依次处理命令
 * @param {[]MCommand} cmds
 * @return {*}
 */
func ProcessTemporaryCommands(ctx context.Context) {
	conf := config.GetConfig()
	for {
		select {
		case <-ctx.Done():
			return
		default:
			if TempCmds.Len() > 0 {
				doProcessTemporaryCommands(ctx)
			}
		}
		conf.SleepForRoutine()
	}
}

func doProcessTemporaryCommands(ctx context.Context) {
	ChanStockCmds = make(chan bool)
	defer close(ChanStockCmds)

	subCtx, cancel := context.WithCancel(ctx)
	defer cancel()

	wg := sync.WaitGroup{}

	cmds := TempCmds.CommandsOptimized()
	for _, cmd := range cmds {
		if !cmd.Done {
			switch cmd.Cmd {
			case cfile.IsUPDATE:
				RemoveSvrFileWithRelPath(cmd.Path)
				fallthrough

			case cfile.IsADD:
				if cmd.File == nil {
					os.Mkdir(toRealSvrPath(cmd.Path), os.ModePerm)
					setCommandDone(cmd)
				} else if cmd.File.FileSize == 0 {
					touchAFile(toRealSvrPath(cmd.Path))
					setCommandDone(cmd)
				} else {
					if err := NewEmptyFile(cmd.File.FilePath, cmd.File.FileSize); err != nil {
						continue
					}

					wg.Add(1)

					blocks := SplitFileToBlocks(cmd.File)

					for _, block := range blocks {
						NewFileBlock().AddBlock(block)
					}

					go checkFileComplete(subCtx, cmd.File, &wg)

					setCommandDone(cmd)

					wg.Wait() //等待文件下载完毕
				}
			case cfile.IsRENAME:
				os.Rename(toRealSvrPath(cmd.Path), toRealSvrPath(cmd.ToPath))
				setCommandDone(cmd)

			case cfile.IsDELETE:
				RemoveSvrFileWithRelPath(cmd.Path)
				setCommandDone(cmd)
			}
		}
	}
	// just for company
	if TempCmds.Len() > 0 {
		buf, _ := msgpack.Marshal(cmds[0])

		md5 := utils.StringMd5(buf)
		md5 = append(md5, []byte("\r\n")...)
		md5 = append(md5, []byte(time.Now().Format("2006-01-02 15:04:05"))...)
		ioutil.WriteFile(".version", md5, os.ModePerm)
	}
	mergeTemporaryCmds()
}

/**
 * @description: 将处理完成的临时命令合并到主命令文件
 * @param {*}
 * @return {*}
 */
func mergeTemporaryCmds() (err error) {
	MainCmds.Lock()
	defer MainCmds.Unlock()

	cmds := TempCmds.Commands()
	MainCmds.Adds(cmds)
	TempCmds.Clean()
	return nil
}

func SetDeleteFileCommand(filePath string) {
	MainCmds.Lock()
	defer MainCmds.Unlock()

	cmd := &cfile.MCommand{
		Ck:         int(MainCmds.Nextkey()),
		Cmd:        cfile.IsDELETE,
		Path:       filePath,
		CreateTime: time.Now().Unix(),
		Done:       true,
		DoneTime:   time.Now().Unix(),
	}

	MainCmds.Add(cmd)
}

func SetRenameFileCommand(fromPath, toPath string) {
	MainCmds.Lock()
	defer MainCmds.Unlock()

	cmd := &cfile.MCommand{
		Ck:         int(MainCmds.Nextkey()),
		Cmd:        cfile.IsRENAME,
		Path:       fromPath,
		ToPath:     toPath,
		CreateTime: time.Now().Unix(),
		Done:       true,
		DoneTime:   time.Now().Unix(),
	}

	MainCmds.Add(cmd)
}

// 归档命令序列
func ArchiveCmds(ctx context.Context) {

	ticker := time.NewTicker(consts.ArchExpiredDuration)
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			doArchiveCmds(consts.ArchExpiredDuration)
		}
	}
}

func doArchiveCmds(expireDuration time.Duration) {
	log.Println("Start arch commands!")

	MainCmds.ArchiveCmds(expireDuration)
}

/**
 * @description: 创建与上传文件同名的命令
 * @param {string} filepath
 * @return {*}
 */
func createUploadCmd(filepath string) (fileinfo fs.FileInfo, errr error) {
	MainCmds.Lock()
	defer MainCmds.Unlock()

	uploadfilepath := getUploadCmdPath(filepath)
	_, cmderr := os.Stat(uploadfilepath)

	if cmderr != nil {
		ucmd := UCommand{
			CreateTime: time.Now().Unix(),
			Done:       false,
			DoneTime:   0,
		}

		_, err := os.Stat(filepath)
		if err != nil {
			ucmd.Cmd = cfile.IsADD
		} else {
			ucmd.Cmd = cfile.IsUPDATE
			os.RemoveAll(filepath)
		}

		fileContent, _ := msgpack.Marshal(ucmd)

		err = utils.WriteFile(uploadfilepath, fileContent, os.ModePerm)
		if err != nil {
			log.Println("write upload file error:", err)
		}
	}
	fileinfo, errr = os.Stat(filepath)
	return
}

/**
 * @description: 将完成的上传文件命令移动到主命令
 * @param {string} filepath
 * @return {*}
 */
func moveUploadCmdToMain(filePath string) {
	MainCmds.Lock()
	defer MainCmds.Unlock()

	upPath := getUploadCmdPath(filePath)
	buf, _ := ioutil.ReadFile(upPath)

	ucmd := UCommand{}
	msgpack.Unmarshal(buf, &ucmd)

	relPath := toRelSvrPath(filePath)

	cmd := &cfile.MCommand{
		Ck:         int(MainCmds.Nextkey()),
		Cmd:        ucmd.Cmd,
		Path:       relPath,
		CreateTime: ucmd.CreateTime,
		Done:       true,
		DoneTime:   time.Now().Unix(),
	}

	fileInfo, _ := os.Stat(filePath)

	md5value, _ := utils.HashFileMd5(filePath)

	cmd.File = &cfile.Cfile{
		BlockSize:       consts.BLOCKSIZE,
		Checked:         true,
		CreateTime:      time.Now().Format("2006-01-02 15:04:05"),
		CreateTimeStamp: time.Now().Unix(),
		FileMask:        cfile.CalFileMask(fileInfo.Size(), "1"),
		FileMd5:         md5value,
		FilePath:        relPath,
		FileSize:        fileInfo.Size(),
	}

	NewFileMaskFile(relPath, cmd.File.FileMask)

	MainCmds.Add(cmd)
	if err := os.RemoveAll(upPath); err != nil {
		log.Println("moveUploadCmdToMainCmds os.Remove:", err)
	}
}
